package cyce.file;

import java.io.Serializable;
import java.math.BigInteger;

import org.jdom.Element;

/**
 * File represents a file stored in the distributed file system
 * @author Colin Rhodes
 *
 */
public class File implements Serializable {
	
	private static final long serialVersionUID = -27119363111817105L;
	private String fileName;
	private String parentPath;
	private String hash;
	
	/**
	 * @param parent The Directory containing this file
	 * @param fileName The name of this file in the distr file system (not local filename)
	 * @param parentPath The path of the directory into which to insert the file
	 * @param hash The hash of the file data
	 */
	protected File(String fileName, String parentPath, String hash) {
		validateAll(fileName, parentPath, hash);
		this.fileName = fileName;
		this.parentPath = parentPath;
		this.hash = hash;
	}
	
	/**
	 * Called by constructor to validate that parameters are valid
	 * Can be overridden to apply different restrictions
	 */
	protected boolean validateAll(String name, String parentPath, String hash) {
		if (!validateName(name)) 
			throw new IllegalArgumentException("File name must be a non-empty string not containing '/'");
		if (!validateParentPath(parentPath))
			throw new IllegalArgumentException("invalid path");
		if (!validateHash(parentPath))
			throw new IllegalArgumentException("invalid hash");
		return true;
	}
	
	protected boolean validateName(String name) {
		if (name == null) return false;
		if (name.equals("")) return false;
		if (name.contains("/")) return false;
		return true;
	}
	
	protected boolean validateParentPath(String path) {
		if (path == null) return true;
		if (path.length() == 0) return false;
		if (path.charAt(0) != '/') return false;
		if (path.contains("//")) return false;
		return true;
	}
	
	protected boolean validateHash(String hash) {
		if (hash == null) return false;
		return true;
	}
	
	/**
	 * Create a File object from a JDOM Element
	 * @param element Element representing a file
	 * @param parentPath Path of parent directory (use null to grab from XML)
	 * @return The deserialized File object
	 */
	protected static File deserialize(Element element, String parentPath) {
		if (element.getName() != "file")
			throw new IllegalArgumentException("Element does not represent a valid File");
		
		String name = element.getAttributeValue("name");
		String path = (parentPath == null ? element.getAttributeValue("path") : parentPath);
		String hash = element.getAttributeValue("hash");
		return new File(name, path, hash);
	}
	
	/**
	 * Get the filename (no path)
	 * @return The filename
	 */
	public String getName() {
		return this.fileName;
	}
	
	@Override
	public String toString() {
		return this.getName();
	}
	
	/**
	 * List the directory structure for testing purposes
	 * @return
	 */
	protected String list(String prefix) {
		return prefix + this.getName();
	}
	
	/**
	 * Get the absolute path of the File (as stored in distributed file system).
	 * The path includes the filename.
	 * The path of the root directory is "/".
	 * The path of all directories ends in "/".
	 * @return The absolute path of this file
	 */
	public String getPath() {
		return this.getParentPath() + this.getName();
	}
	
	/**
	 * Get the absolute path of the parent of this file (as stored in distributed file system).
	 * The path of the root directory is "/".
	 * The path of all directories ends in "/".
	 * @return The absolute path of the parent of this file, or null if this file is root
	 */
	public String getParentPath() {
		return this.parentPath;
	}
	
	/**
	 * Get whether this File is the root directory
	 * @return True if this File is a root directory
	 */
	public boolean isRoot() {
		return false;
	}
	
	/**
	 * Get a the curent file if the path matches this File's path 
	 * @param path The path of the file to get
	 * @return The current file if the path matches; otherwise null
	 */
	public File getFile(String path) {
		if (this.getPath().equals(path))
			return this;
		else
			return null;
	}
	
	/**
	 * Get a the current file if the hash matches this File's hash
	 * @param hash The hash of the File
	 * @return The current file if the hash matches; otherwise null
	 */
	public File getFileByHash(String hash) {
		if (this.getHash().equals(hash))
			return this;
		else
			return null;
	}
	
	/**
	 * Get the hash code of the file data that this File represents
	 * @return The hash code
	 */
	public String getHash() {
		return this.hash;
	}
	
	/**
	 * Represent the data of this File in a JDOM Element
	 * @return A JDOM Element with the data of this File
	 */
	protected Element serialize() {
		Element file = new Element("file");
		file.setAttribute("name", this.getName());
		file.setAttribute("hash", this.getHash());
		file.setAttribute("path", this.getPath());
		return file;
	}
	
	
	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Object obj)
    {       
        if (obj != null && (obj.getClass().equals(this.getClass())))
        {
            File d = (File)obj;
            //assume hash is a unique identifier
            return d.hash.equals(this.hash);
        }
        return false;
    }
	
	public int hashCode()
	{
		if (this.hash == null || this.hash.length() == 0) return 0;
		BigInteger bi = new BigInteger(this.hash, 16);
		return bi.intValue();
	}

}
